/**
 * NOTE:
 * 使用信号量而非 Mutex 的原因，Mutex 在一些平台不支持在获取到锁之前先做释放动作
 * 使用场景:
 * Thread A 打算通过锁 M 等待事件 E 完成
 * 但 E 在 A 上锁前提前完成，也就是先解锁，然后 A 上锁，那么本次上锁应该抵消，A 不被锁住
 * 但 Linux 平台 pthread mutex 的不允许此操作，而且会导致奇怪的异常
 * 
 * 这个问题用信号量就可以很好的解决
 * 
 */
#undef  xuser
#define xuser mixc::concurrency_thread_self::inc
#include"configure/limit.hpp"
#include"configure/platform.hpp"
#include"configure/switch.hpp"
#include"concurrency/thread.hpp"
#include"concurrency/thread_self.hpp"
#include"gc/private/gc_background.hpp"
#include"macro/xinit.hpp"
#include"macro/xthread_local.hpp"
#include"macro/xmodify.hpp"
#include"memop/cast.hpp"

#if xuse_mixc_rtos
    #include"extern/rtos/task.hpp"
#elif xis_windows
    #include<windows.h>
#elif xis_linux
    #include<pthread.h>
    #include<semaphore.h>
    #include<time.h>
    #include<unistd.h>
#endif

namespace mixc::concurrency_thread{
    #if xuse_mixc_rtos
        // pass
    #else
        #ifndef xdecl_l_lambda
            #define xdecl_l_lambda
            xthread_local(l_lambda, clambda);
        #endif
    #endif
}

namespace mixc::concurrency_thread_self::origin{
    #if xuse_mixc_rtos
        // pass
    #elif xis_windows
        // windows 用 mutex 无法阻塞主线程
        inline voidp h_for_suspend;
    #else
        inline sem_t h_for_suspend;
    #endif

    xinit(inc::the_concurrency_thread_self){
    #if xuse_mixc_rtos
        ; // pass
    #elif xis_windows
        // 用 mutex 无法阻塞主线程
        h_for_suspend   = CreateSemaphoreA(
            nullptr, 
            0/*初始值*/, 
            inc::limit::max_thread/*最大值*/, 
            nullptr
        );
    #elif xis_linux
        sem_init(& h_for_suspend, 0, 0);
    #else
        #error "pending"
    #endif
    };

    bool thread_self::is_main_thread(){
    #if xuse_mixc_rtos
        static_assert(sizeof(inc::g_task_idle) == sizeof(uxx));

        // 约定：idle 任务为 Mix-C 中的主线程
        return inc::cast<uxx>(inc::g_task_idle) == thread_self::id();
    #else
        return id() == 0;
    #endif
    }

    uxx thread_self::id(){
    #if xuse_mixc_rtos
        static_assert(sizeof(inc::g_task_self) == sizeof(uxx));
        return inc::cast<uxx>(inc::g_task_self);
    #else
        return inc::cast<uxx>(*mixc::concurrency_thread::l_lambda);
    #endif
    }

    void thread_self::resume(uxx thread_id){
        if (thread_id == not_exist){
            return;
        }

    #if xuse_mixc_rtos
        inc::cast<inc::task>(thread_id).resume();
    #else
        using namespace mixc::concurrency_thread;
        auto the_thread     = inc::cast<clambda>(thread_id);
        auto is_main        = thread_id == 0;

        #if xis_windows
            ReleaseSemaphore(is_main ? h_for_suspend : the_thread->semaphore_for_suspend(), 1, nullptr);
        #elif xis_linux
            sem_post(
                is_main ? xmodify(h_for_suspend) : (sem_t *)the_thread->semaphore_for_suspend()
            );
        #else
            #error "pending"
        #endif
    #endif
    }

    void thread_self::suspend(uxx timeout){
        using namespace mixc::concurrency_thread;

    #if xuse_mixc_rtos
        if (timeout == not_exist){
            inc::g_task_self.suspend();
        }
        else{
            inc::g_task_self.suspend(timeout);
        }
    #elif xis_windows
        auto is_main    = is_main_thread();
        WaitForSingleObject(
            is_main ? h_for_suspend : (*l_lambda)->semaphore_for_suspend(), 
            DWORD(timeout)
        );
    #elif xis_linux
        auto is_main    = is_main_thread();
        auto sem        = is_main ? 
            xmodify(h_for_suspend) : (sem_t *)(*l_lambda)->semaphore_for_suspend();

        if (timeout == not_exist){
            sem_wait(sem);
            return;
        }

        timespec time{};
        clock_gettime(CLOCK_REALTIME, & time);
        time.tv_nsec   += timeout % 1000 * 1'000'000;
        time.tv_sec    += timeout / 1000 + time.tv_nsec / 1'000'000'000;
        time.tv_nsec   %= 1'000'000'000;
        sem_timedwait(sem, xmodify(time));
    #endif
    }

    void thread_self::sleep(uxx millisecond){
    #if xuse_mixc_rtos
        inc::g_task_self.suspend(millisecond);
    #elif xis_windows
        Sleep(DWORD(millisecond));
    #elif xis_linux
        while(true){
            if (millisecond > 1000){
                millisecond -= 1000;
                usleep(1000 * 1000);
            }
            else{
                usleep(millisecond * 1000);
                break;
            }
        }
    #else
        #error "pending"
    #endif
    }

    void thread_self::yield(){
    #if xuse_mixc_rtos
        inc::g_task_self.yield();
    #elif xis_windows
        SwitchToThread();
    #elif xis_linux
        pthread_yield();
    #else
        #error "pending"
    #endif
    }

    void thread_self::gc_sync(){
    #if xuse_gc
        inc::gc_sync();
    #endif
    }

    inc::gc_performance_counter thread_self::gc_performance(){
        auto result         = inc::gc_performance_counter{};
    #if xuse_gc
    #if xuse_gc_performance_counter
        result              = inc::l_pc;
    #endif
    #endif
        return result;
    }
}
